package com.googlecode.biglog.appender.orm;

import java.util.Date;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import net.sf.common.util.BeanUtils;
import net.sf.common.util.ValueUtils;

import org.apache.log4j.spi.ErrorCode;
import org.apache.log4j.spi.LoggingEvent;
import org.dbist.dml.Dml;

import com.googlecode.biglog.appender.AbstractModelAppender;

public class DmlAppender extends AbstractModelAppender {
	private String applicationContextName;
	private String dmlBeanName;
	private long delay = 20000;
	public String getApplicationContextName() {
		return applicationContextName;
	}
	public void setApplicationContextName(String applicationContextName) {
		this.applicationContextName = applicationContextName;
	}
	public String getDmlBeanName() {
		return dmlBeanName;
	}
	public void setDmlBeanName(String dmlBeanName) {
		this.dmlBeanName = dmlBeanName;
	}

	private Dml dml;
	private BeanUtils beanUtils;

	private boolean initialized = false;
	@Override
	public boolean isInitialized() {
		return initialized && super.isInitialized();
	}

	@Override
	public void activateOptions() {
		super.activateOptions();

		try {
			close();

			if (ValueUtils.isEmpty(applicationContextName))
				throw new IllegalArgumentException("applicationContextName property is required.");

			if (ValueUtils.isEmpty(dmlBeanName))
				throw new IllegalArgumentException("dmlBeanName property is required.");

			initialized = true;
		} catch (Exception e) {
			errorHandler.error("Unexpected exception while initialising MongoDbAppender.", e, ErrorCode.GENERIC_FAILURE);
		}
	}

	@Override
	public void close() {
		dml = null;
	}

	private boolean stopped = false;
	private long startTime = 0;
	private Map<Long, Boolean> threadStopped = new ConcurrentHashMap<Long, Boolean>();
	@Override
	protected void append(final LoggingEvent loggingEvent) {
		if (loggingEvent == null || !isInitialized() || stopped || threadStopped.containsKey(Thread.currentThread().getId()))
			return;

		if (stopped)
			return;

		if (dml == null) {
			if (beanUtils == null) {
				stopped = true;
				startTime = new Date().getTime();
				try {
					beanUtils = BeanUtils.getInstance(applicationContextName);
				} finally {
					stopped = false;
				}
				if (beanUtils == null)
					return;
				return;
			}

			if (new Date().getTime() - startTime < delay)
				return;

			Dml temp;
			try {
				stopped = true;
				temp = beanUtils.get(dmlBeanName, Dml.class);
			} catch (Exception e) {
				return;
			} finally {
				stopped = false;
			}

			if (getModelClass() == null) {
				try {
					stopped = true;
					setModelClass(temp.getTable(getModelName()).getClazz());
				} catch (Exception e) {
					return;
				} finally {
					stopped = false;
				}
			}

			dml = temp;
		}

		Map<String, Object> map = toMap(loggingEvent);
		if (map == null)
			return;

		Object log;
		try {
			log = ValueUtils.populate(map, getModelClass().newInstance());
		} catch (Exception e) {
			e.printStackTrace();
			return;
		}

		Long threadId = Thread.currentThread().getId();
		try {
			threadStopped.put(threadId, true);
			dml.insert(log);
		} catch (Exception e) {
			errorHandler.error("Failed to insert document to MongoDB", e, ErrorCode.WRITE_FAILURE);
		} finally {
			threadStopped.remove(threadId);
		}
	}
}
